/*
 * Copyright (c) 2010-present www.walkerljl.org All Rights Reserved.
 * The software source code all copyright belongs to the author, 
 * without permission shall not be any reproduction and transmission.
 */
package org.walkerljl.commons.mail;

import javax.mail.*;

/**
 * ReceiveMailSession
 *
 * @author lijunlin
 */
public class ReceiveMailSession {

    protected static final String DEFAULT_FOLDER = "INBOX";

    static {
        MailConstants.mailSystem.defineJavaMailSystemProperties();
    }

    protected final Session session;
    protected final Store store;
    protected Folder folder;

    /**
     * Creates new mail session.
     */
    public ReceiveMailSession(Session session, Store store) {
        this.session = session;
        this.store = store;
    }

    /**
     * Opens session.
     */
    public void open() {
        try {
            store.connect();
        } catch (MessagingException msex) {
            throw new MailException("Open session error", msex);
        }
    }

    // ---------------------------------------------------------------- folders

    /**
     * Returns list of all folders. You can use these names in
     * {@link #useFolder(String)} method.
     */
    public String[] getAllFolders() {
        Folder[] folders;
        try {
            folders = store.getDefaultFolder().list("*");
        } catch (MessagingException msex) {
            throw new MailException("Failed to connect to folder", msex);
        }
        String[] folderNames = new String[folders.length];

        for (int i = 0; i < folders.length; i++) {
            Folder folder = folders[i];
            folderNames[i] = folder.getFullName();
        }
        return folderNames;
    }

    /**
     * Opens new folder and closes previously opened folder.
     */
    public void useFolder(String folderName) {
        closeFolderIfOpened();
        try {
            folder = store.getFolder(folderName);
        } catch (MessagingException msex) {
            throw new MailException("Failed to connect to folder: " + folderName, msex);
        }
        try {
            folder.open(Folder.READ_WRITE);
        } catch (MessagingException ignore) {
            try {
                folder.open(Folder.READ_ONLY);
            } catch (MessagingException msex) {
                throw new MailException("Failed to open folder: " + folderName, msex);
            }
        }
    }

    /**
     * Opens default folder: INBOX.
     */
    public void useDefaultFolder() {
        closeFolderIfOpened();
        useFolder(DEFAULT_FOLDER);
    }

    // ---------------------------------------------------------------- message count

    /**
     * Returns number of messages.
     */
    public int getMessageCount() {
        if (folder == null) {
            useDefaultFolder();
        }
        try {
            return folder.getMessageCount();
        } catch (MessagingException mex) {
            throw new MailException(mex);
        }
    }

    /**
     * Returns the number of new messages.
     */
    public int getNewMessageCount() {
        if (folder == null) {
            useDefaultFolder();
        }
        try {
            return folder.getNewMessageCount();
        } catch (MessagingException mex) {
            throw new MailException(mex);
        }
    }

    /**
     * Returns the number of unread messages.
     */
    public int getUnreadMessageCount() {
        if (folder == null) {
            useDefaultFolder();
        }
        try {
            return folder.getUnreadMessageCount();
        } catch (MessagingException mex) {
            throw new MailException(mex);
        }
    }

    /**
     * Returns the number of deleted messages.
     */
    public int getDeletedMessageCount() {
        if (folder == null) {
            useDefaultFolder();
        }
        try {
            return folder.getDeletedMessageCount();
        } catch (MessagingException mex) {
            throw new MailException(mex);
        }
    }

    // ---------------------------------------------------------------- receive emails

    /**
     * Receives all emails. Messages are not modified. However, servers
     * do may set SEEN flag anyway, so we force messages to remain
     * unseen.
     */
    public ReceivedEmail[] receiveEmail() {
        return receive(null, null);
    }

    /**
     * Receives all emails that matches given {@link EmailFilter filter}.
     * Messages are not modified. However, servers do may set SEEN flag anyway,
     * so we force messages to remain unseen.
     */
    public ReceivedEmail[] receiveEmail(EmailFilter emailFilter) {
        return receive(emailFilter, null);
    }

    /**
     * Receives all emails and mark all messages as 'seen' (ie 'read').
     */
    public ReceivedEmail[] receiveEmailAndMarkSeen() {
        return receiveEmailAndMarkSeen(null);
    }

    /**
     * Receives all emails that matches given {@link EmailFilter filter}
     * and mark them as 'seen' (ie 'read').
     */
    public ReceivedEmail[] receiveEmailAndMarkSeen(EmailFilter emailFilter) {
        Flags flags = new Flags();
        flags.add(Flags.Flag.SEEN);
        return receive(emailFilter, flags);
    }

    /**
     * Receives all emails and mark all messages as 'seen' and 'deleted'.
     */
    public ReceivedEmail[] receiveEmailAndDelete() {
        return receiveEmailAndDelete(null);
    }

    /**
     * Receives all emails that matches given {@link EmailFilter filter} and
     * mark all messages as 'seen' and 'deleted'.
     */
    public ReceivedEmail[] receiveEmailAndDelete(EmailFilter emailFilter) {
        Flags flags = new Flags();
        flags.add(Flags.Flag.SEEN);
        flags.add(Flags.Flag.DELETED);
        return receive(emailFilter, flags);
    }

    /**
     * Receives all emails that matches given {@link EmailFilter filter}
     * and set given flags. Both filter and flags to set are optional.
     * If flags to set is not provided, it forces 'seen' flag to be unset.
     */
    public ReceivedEmail[] receive(EmailFilter filter, Flags flagsToSet) {
        if (folder == null) {
            useDefaultFolder();
        }

        Message[] messages;

        // todo add FetchProfile option for just headers

        try {
            if (filter == null) {
                messages = folder.getMessages();
            } else {
                messages = folder.search(filter.getSearchTerm());
            }

            if (messages.length == 0) {
                return null;
            }

            // process messages

            ReceivedEmail[] emails = new ReceivedEmail[messages.length];

            for (int i = 0; i < messages.length; i++) {
                Message msg = messages[i];

                if (flagsToSet != null) {
                    msg.setFlags(flagsToSet, true);
                }
                emails[i] = new ReceivedEmail(msg);

                if (flagsToSet == null && emails[i].isSeen() == false) {
                    msg.setFlag(Flags.Flag.SEEN, false);
                }
            }

            return emails;
        } catch (MessagingException msex) {
            throw new MailException("Failed to fetch messages", msex);
        }
    }

    // ---------------------------------------------------------------- close

    /**
     * Closes folder if opened and expunge deleted messages.
     */
    protected void closeFolderIfOpened() {
        if (folder != null) {
            try {
                folder.close(true);
            } catch (MessagingException ignore) {
            }
        }
    }

    /**
     * Closes session.
     */
    public void close() {
        closeFolderIfOpened();
        try {
            store.close();
        } catch (MessagingException mex) {
            throw new MailException(mex);
        }
    }
}